//
// Created by Chiro on 2022/2/22.
//
#include <common.h>
#include <verilator_model_test.h>

#ifndef USE_MODEL
#include <verilated.h>
#else

#include <model.h>

#define IS_DIFF_ON IS_DIFF(verilator_model_test)
#define VerilatedTarget MUXDEF(IS_DIFF_ON, VerilatedDiff<verilator_model_test>, VerilatedFake)

class verilator_model_test_sm : public VerilatedTarget {
public:
  const uint8_t clock_max = 10;
  uint8_t a{}, b{}, c{};
  uint8_t cnt{};

  verilator_model_test_sm() : VerilatedTarget() {}

#if IS_DIFF_ON

  void dut_load() override {
    clock = dut->clock;
    reset = dut->reset;
    a = dut->a;
    b = dut->b;
  }

  void dut_apply() override {
    dut->clock = clock, dut->reset = reset;
    dut->a = a, dut->b = b;
    dut->eval();
  }

  bool dut_check() override {
    if (dut->c != c) {
      Log("DUT Check Diff at c: REF <> DUT - 0x%02X <> 0x%02X", c, dut->c);
      return false;
    }
    return true;
  }

#endif

  void eval() override {
    static uint8_t clock_last = 1;
    IFDEF(IS_DIFF_ON, dut_apply());
    // on rising edge
    if (clock_last == 0 && clock == 1) {
      c = a ^ b;
      cnt = (cnt == (clock_max - 1) ? 0 : cnt + 1);
      if (cnt == (clock_max - 1)) final();
    }
    clock_last = clock;
    IFDEF(IS_DIFF_ON, Assert(dut_check(), "DUT Assert Failed"));
  }
};

#define verilator_model_test verilator_model_test_sm
#define Verilated VerilatedTarget
#endif

int main(int argc, char **argv, char **env) {
  auto *top = new verilator_model_test;
  srand(time(nullptr));
  uint32_t cnt = 0;
  auto exec = [&](int n) {
    top->clock = 0;
    top->eval();
    top->clock = 1;
    top->eval();
    cnt++;
  };
  top->reset = 1;
  exec(5);
  top->reset = 0;
  int a = 0, b = 0;
  while (!Verilated::gotFinish()) {
    top->a = a = rand() % 2;
    top->b = b = rand() % 2;
    // Evaluate model
    exec(1);
    printf("[%03lu] ", cnt);
    if (top->c != (a ^ b)) {
      printf("Err: a = %d, b = %d, c = %d\n", a, b, top->c);
    } else {
      printf("OK.\n");
    }
  }
  top->final();
  delete top;
  return 0;
}
